"""
JWT工具类
"""
from datetime import datetime, timedelta
from jose import JWTError, jwt
import bcrypt
from utils.config import config
import logging

logger = logging.getLogger(__name__)

# 直接使用 bcrypt 库，避免 passlib 初始化时的 detect_wrap_bug 问题
# bcrypt 库生成的哈希格式与 passlib 兼容，可以验证 passlib 生成的哈希


class JWTUtils:
    """JWT工具类"""
    
    @staticmethod
    def _safe_truncate_password(password: str, max_bytes: int = 72) -> str:
        """
        安全地截断密码，确保UTF-8编码后不超过指定字节数
        bcrypt 限制密码长度不能超过 72 字节
        
        :param password: 原始密码字符串
        :param max_bytes: 最大字节数（默认72，bcrypt限制）
        :return: 截断后的密码字符串，保证编码后字节数 <= max_bytes
        """
        # 类型检查
        if not isinstance(password, str):
            password = str(password) if password else ""
        
        if not password:
            return ""
        
        # 将密码编码为字节
        password_bytes = password.encode('utf-8')
        
        # 如果不超过限制，直接返回
        if len(password_bytes) <= max_bytes:
            return password
        
        # 截断到最大字节数，但要确保不截断UTF-8字符的中间部分
        truncated_bytes = password_bytes[:max_bytes]
        
        # 移除末尾可能不完整的UTF-8字符
        # UTF-8字符的后续字节以 10xxxxxx 开头（最高两位是 10）
        # 从末尾开始，移除所有后续字节，直到遇到字符的第一个字节
        while truncated_bytes:
            last_byte = truncated_bytes[-1]
            # 检查是否是UTF-8字符的后续字节（以10开头）
            if (last_byte & 0xC0) == 0x80:
                # 这是后续字节，移除它
                truncated_bytes = truncated_bytes[:-1]
            else:
                # 这是字符的第一个字节或单字节字符，可以安全解码
                break
        
        # 如果截断后为空，返回空字符串
        if not truncated_bytes:
            return ""
        
        # 尝试解码
        try:
            result = truncated_bytes.decode('utf-8')
            # 最终验证：确保编码后确实不超过限制
            final_bytes = result.encode('utf-8')
            if len(final_bytes) <= max_bytes:
                return result
            else:
                # 如果仍然超过（极端情况），再次截断
                return JWTUtils._safe_truncate_password(result, max_bytes)
        except UnicodeDecodeError:
            # 如果解码失败，再次移除最后一个字节
            if len(truncated_bytes) > 1:
                truncated_bytes = truncated_bytes[:-1]
                try:
                    return truncated_bytes.decode('utf-8')
                except UnicodeDecodeError:
                    # 如果还是失败，使用错误替换模式
                    return truncated_bytes.decode('utf-8', errors='replace')
            return ""
    
    @staticmethod
    def verify_password(plain_password: str, hashed_password: str) -> bool:
        """
        验证密码
        :param plain_password: 明文密码
        :param hashed_password: 哈希密码
        :return: 是否匹配
        """
        # 参数类型检查
        if not isinstance(plain_password, str):
            raise TypeError(f"plain_password 必须是字符串类型，当前类型: {type(plain_password)}")
        if not isinstance(hashed_password, str):
            raise TypeError(f"hashed_password 必须是字符串类型，当前类型: {type(hashed_password)}")
        
        if not plain_password or not hashed_password:
            return False
        
        # bcrypt 限制密码长度不能超过 72 字节
        # 使用安全截断方法确保密码在编码后不超过72字节
        safe_password = JWTUtils._safe_truncate_password(plain_password, max_bytes=72)
        
        # 最终验证：确保密码字节长度严格不超过72字节（双重保险）
        # 这是调用 bcrypt 之前的最后一道防线
        password_bytes = safe_password.encode('utf-8')
        if len(password_bytes) > 72:
            # 如果仍然超过（极端情况），再次调用截断方法
            safe_password = JWTUtils._safe_truncate_password(safe_password, max_bytes=72)
            password_bytes = safe_password.encode('utf-8')
            # 如果还是超过，使用最严格的方式：直接截断字节并替换错误字符
            if len(password_bytes) > 72:
                safe_password = password_bytes[:72].decode('utf-8', errors='replace')
        
        try:
            # 直接使用 bcrypt 库验证密码
            # bcrypt.checkpw 需要字节类型，hashed_password 是字符串
            password_bytes = safe_password.encode('utf-8')
            hashed_bytes = hashed_password.encode('utf-8')
            return bcrypt.checkpw(password_bytes, hashed_bytes)
        except ValueError as e:
            error_msg = str(e)
            password_len = len(safe_password.encode('utf-8'))
            logger.error(
                f"密码验证失败 - ValueError: {e}, 密码字节长度: {password_len}",
                exc_info=True
            )
            return False
        except Exception as e:
            # 记录其他错误但不泄露细节
            logger.error(f"密码验证过程出错: {e}", exc_info=True)
            return False
    
    @staticmethod
    def get_password_hash(password: str) -> str:
        """
        获取密码哈希
        :param password: 明文密码
        :return: 哈希密码
        """
        # 参数类型检查
        if not isinstance(password, str):
            raise TypeError(f"password 必须是字符串类型，当前类型: {type(password)}")
        
        if not password:
            raise ValueError("密码不能为空")
        
        # bcrypt 限制密码长度不能超过 72 字节
        # 使用安全截断方法确保密码在编码后不超过72字节
        safe_password = JWTUtils._safe_truncate_password(password, max_bytes=72)
        
        # 最终验证：确保密码字节长度严格不超过72字节（双重保险）
        password_bytes = safe_password.encode('utf-8')
        if len(password_bytes) > 72:
            # 如果仍然超过（极端情况），再次调用截断方法
            safe_password = JWTUtils._safe_truncate_password(safe_password, max_bytes=72)
            password_bytes = safe_password.encode('utf-8')
            # 如果还是超过，使用最严格的方式：直接截断字节并替换错误字符
            if len(password_bytes) > 72:
                safe_password = password_bytes[:72].decode('utf-8', errors='replace')
                password_bytes = safe_password.encode('utf-8')
        
        # 直接使用 bcrypt 库生成密码哈希
        # bcrypt.gensalt() 生成盐值，bcrypt.hashpw 生成哈希
        salt = bcrypt.gensalt()
        hashed = bcrypt.hashpw(password_bytes, salt)
        # 返回字符串格式的哈希值（与 passlib 格式兼容）
        return hashed.decode('utf-8')
    
    @staticmethod
    def create_access_token(data: dict, expires_delta: timedelta = None) -> str:
        """
        创建访问令牌
        :param data: 要编码的数据
        :param expires_delta: 过期时间增量
        :return: JWT令牌
        """
        to_encode = data.copy()
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(minutes=config.JWT_ACCESS_TOKEN_EXPIRE_MINUTES)
        
        to_encode.update({"exp": expire})
        encoded_jwt = jwt.encode(to_encode, config.JWT_SECRET_KEY, algorithm=config.JWT_ALGORITHM)
        return encoded_jwt
    
    @staticmethod
    def decode_access_token(token: str) -> dict:
        """
        解码访问令牌
        :param token: JWT令牌
        :return: 解码后的数据
        """
        try:
            payload = jwt.decode(token, config.JWT_SECRET_KEY, algorithms=[config.JWT_ALGORITHM])
            return payload
        except JWTError:
            return None


